--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit 747fb2531da0c1765299809607d6df1b3177724e
Parents : 6c5aec9
Author : Ivan <ivan@quad4.io>
Signature : Invalid signer <e46112d44649266d71fe2193e00a4710>, author is <ivan@quad4.io>
Date : 2026-07-09T08:29:49-05:00
feat(Android): add support for syncing vendored lxmfy package into Chaquopy Python sources, enhancing bot functionality. Update README and build.gradle to reflect changes, and add tests to verify integration.
Changes
4 files changed, 40 insertions(+), 2 deletions(-)
Diff
diff --git a/android/.gitignore b/android/.gitignore
index 7fb782ae..0cdc2998 100644
--- a/android/.gitignore
+++ b/android/.gitignore
@@ -16,6 +16,7 @@ local.properties
# Gradle-synced Chaquopy Python tree (see app/build.gradle)
/app/src/main/python/meshchatx/
+/app/src/main/python/lxmfy/
# libcodec2.so copied from vendor wheels (scripts/android/sync-codec2-jni-libs.sh)
/app/src/main/jniLibs/*/libcodec2.so
diff --git a/android/README.md b/android/README.md
index eb6cfbe5..ce3d8a57 100644
--- a/android/README.md
+++ b/android/README.md
@@ -28,7 +28,7 @@ cd android
./gradlew --no-daemon :app:assembleDebug :app:assembleRelease
```
-There is a **single** application variant (no product flavors). Gradle syncs the **entire** `meshchatx/` tree into `app/src/main/python/meshchatx/` (including `public/repository-server-bundled` for the in-app repository server). The `fetchRepositoryBundledWheels` task runs before sync when bundled wheels are missing; if repo root `dist/reticulum_meshchatx-*.whl` exists (e.g. from `python -m build --wheel -o dist .`), that wheel is preferred over PyPI for the bundled set.
+There is a **single** application variant (no product flavors). Gradle syncs the **entire** `meshchatx/` tree into `app/src/main/python/meshchatx/` (including `public/repository-server-bundled` for the in-app repository server), and syncs vendored **`vendor/lxmfy/lxmfy`** into `app/src/main/python/lxmfy/` (required for bots; not installed via Chaquopy pip). The `fetchRepositoryBundledWheels` task runs before sync when bundled wheels are missing; if repo root `dist/reticulum_meshchatx-*.whl` exists (e.g. from `python -m build --wheel -o dist .`), that wheel is preferred over PyPI for the bundled set.
### Native ABIs (universal APK)
diff --git a/android/app/build.gradle b/android/app/build.gradle
index d20bcbbc..b12954c9 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -5,6 +5,7 @@ plugins {
def repoRoot = rootProject.projectDir.parentFile
def meshchatSourceDir = new File(repoRoot, "meshchatx")
+def lxmfySourceDir = new File(repoRoot, "vendor/lxmfy/lxmfy")
def repositoryBundledWheelsDir = new File(meshchatSourceDir, "public/repository-server-bundled/bundled")
def chaquopyBuildPython = System.getenv("CHAQUOPY_BUILD_PYTHON")
def pythonTempDir = new File(buildDir, "python-tmp")
@@ -192,9 +193,33 @@ tasks.register("syncMeshchatPython", Sync) {
into(file("$projectDir/src/main/python/meshchatx"))
}
+/*
+ * Desktop installs lxmfy via setuptools (pyproject where=[".", "vendor/lxmfy"]).
+ * Chaquopy only sees android/app/src/main/python plus pip installs, so the
+ * vendored package must be synced here or bot_templates import fails at boot.
+ */
+tasks.register("syncLxmfyPython", Sync) {
+ if (!lxmfySourceDir.exists()) {
+ throw new org.gradle.api.GradleException(
+ "Missing vendored lxmfy package at ${lxmfySourceDir}. " +
+ "Expected vendor/lxmfy/lxmfy from the MeshChatX tree."
+ )
+ }
+ def initPy = new File(lxmfySourceDir, "__init__.py")
+ if (!initPy.exists()) {
+ throw new org.gradle.api.GradleException("Missing ${initPy}; vendored lxmfy tree looks incomplete.")
+ }
+ doFirst {
+ delete(file("$projectDir/src/main/python/lxmfy"))
+ }
+ from(lxmfySourceDir)
+ includeEmptyDirs = false
+ into(file("$projectDir/src/main/python/lxmfy"))
+}
+
tasks.configureEach { task ->
if (task.name.toLowerCase().contains("merge") && task.name.toLowerCase().contains("pythonsources")) {
- task.dependsOn(tasks.named("syncMeshchatPython"))
+ task.dependsOn(tasks.named("syncMeshchatPython"), tasks.named("syncLxmfyPython"))
}
}
diff --git a/tests/frontend/behaviorContracts.test.js b/tests/frontend/behaviorContracts.test.js
index 13a697bc..c1ab4059 100644
--- a/tests/frontend/behaviorContracts.test.js
+++ b/tests/frontend/behaviorContracts.test.js
@@ -150,3 +150,15 @@ describe("behavior contracts: dead API surface", () => {
expect(viewer).toContain("cancelSendingMessage(");
});
});
+
+describe("behavior contracts: Android Chaquopy Python sync", () => {
+ it("Gradle syncs vendored lxmfy into Chaquopy python sources", () => {
+ const gradle = readSource("android/app/build.gradle");
+ expect(gradle).toContain('vendor/lxmfy/lxmfy');
+ expect(gradle).toContain('syncLxmfyPython');
+ expect(gradle).toContain('src/main/python/lxmfy');
+ expect(gradle).toMatch(/dependsOn\(tasks\.named\("syncMeshchatPython"\),\s*tasks\.named\("syncLxmfyPython"\)\)/);
+ const initPy = readSource("vendor/lxmfy/lxmfy/__init__.py");
+ expect(initPy.length).toBeGreaterThan(0);
+ });
+});
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────